package com.basker.pisces.core.meta.bean;

import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import org.springframework.util.ClassUtils;

import com.basker.pisces.core.DataContractGenerator;
import com.basker.pisces.core.meta.IDataObjectMeta;
import com.basker.pisces.core.meta.ObjectMetaNameHelper;
import com.basker.pisces.core.meta.utils.DataObjectMetaUtils;

public class BeanMetaManager {
    private static Map<String, BeanMeta> META_MAP = new HashMap<String, BeanMeta>();
    private static ThreadLocal<Map<String, BeanMeta>> THREAD_LOCAL_META_MAP = new ThreadLocal<>();

    public static IDataObjectMeta getBeanMeta(Class<?> clazz) {
        clazz = resolveClass(clazz);
        String key = clazz.getName();

        // 首先从缓存获取
        BeanMeta beanMeta = META_MAP.get(key);
        if (beanMeta != null) {
            return beanMeta;
        }

        // 其当前线程缓存获取，只有构建生命周期中的meta才会存在于这个容器
        beanMeta = getFromThreadLocal(key);
        if (beanMeta != null) {
            return beanMeta;
        }

        if (beanMeta == null) {
            synchronized (META_MAP) {
                beanMeta = META_MAP.get(key);

                if (beanMeta == null) {
                    beanMeta = createBeanMeta(key, clazz);
                    META_MAP.put(key, beanMeta);
                }
            }
        }

        return beanMeta;
    }

    public static IDataObjectMeta getPartBeanMeta(Class<?> clazz, String[] loadFields) {
        return getPartBeanMeta(clazz, loadFields, false);
    }

    public static IDataObjectMeta getReferenceBeanMeta(Class<?> clazz, String[] loadFields) {
        return getPartBeanMeta(clazz, loadFields, true);
    }

    private static BeanMeta createBeanMeta(String key, Class<?> clazz) {
        BeanMeta beanMeta = new BeanMeta(clazz);
        // 先放入线程缓存，再构建，即使形成引用环也会在THREAD_LOCAL_META_MAP中找到
        putToThreadLocal(key, beanMeta);

        try {
            beanMeta.build();
            return beanMeta;
        } finally {
            removeFromThreadLocal(key);
        }
    }

    private static PartBeanMeta createPartBeanMeta(String key, Class<?> beanClass, String[] loadFields,
                                                   boolean isRefMeta) {
        PartBeanMeta beanMeta = null;

        if (isRefMeta) {
            beanMeta = new ReferenceBeanMeta(beanClass, loadFields);
        } else {
            beanMeta = new PartBeanMeta(beanClass, loadFields);
        }

        // 先放入线程缓存，再构建，即使形成引用环也会在THREAD_LOCAL_META_MAP中找到
        putToThreadLocal(key, beanMeta);

        try {
            beanMeta.build();
            return beanMeta;
        } finally {
            removeFromThreadLocal(key);
        }
    }

    private static BeanMeta getFromThreadLocal(String key) {
        Map<String, BeanMeta> map = THREAD_LOCAL_META_MAP.get();
        return map != null ? map.get(key) : null;
    }

    private static IDataObjectMeta getPartBeanMeta(Class<?> clazz, String[] loadFields, boolean isRefMeta) {
        clazz = resolveClass(clazz);

        IDataObjectMeta fullMeta = getBeanMeta(clazz);
        List<String> validFields = DataObjectMetaUtils.getValidFields(fullMeta, Arrays.asList(loadFields), isRefMeta);
        loadFields = validFields.toArray(new String[validFields.size()]);

        String key = ObjectMetaNameHelper.generatePartMetaName(clazz.getName(), loadFields);
        if (isRefMeta) {
            key += "_REF";
        }

        // 首先从缓存获取
        BeanMeta partBeanMeta = META_MAP.get(key);
        if (partBeanMeta != null) {
            return partBeanMeta;
        }

        // 其当前线程缓存获取，只有构建生命周期中的meta才会存在于这个容器
        partBeanMeta = getFromThreadLocal(key);
        if (partBeanMeta != null) {
            return partBeanMeta;
        }

        if (partBeanMeta == null) {
            synchronized (META_MAP) {
                partBeanMeta = META_MAP.get(key);

                if (partBeanMeta == null) {
                    partBeanMeta = createPartBeanMeta(key, clazz, loadFields, isRefMeta);
                    META_MAP.put(key, partBeanMeta);
                }
            }
        }

        return partBeanMeta;
    }

    private static void putToThreadLocal(String key, BeanMeta meta) {
        Map<String, BeanMeta> map = THREAD_LOCAL_META_MAP.get();
        if (map == null) {
            map = new HashMap<>();
            THREAD_LOCAL_META_MAP.set(map);
        }

        map.put(key, meta);
    }

    private static void removeFromThreadLocal(String key) {
        Map<String, BeanMeta> map = THREAD_LOCAL_META_MAP.get();
        if (map != null) {
            map.remove(key);

            if (map.isEmpty()) {
                THREAD_LOCAL_META_MAP.remove();
            }
        }
    }

    private static Class<?> resolveClass(Class<?> clazz) {
        if (ClassUtils.isCglibProxyClass(clazz)) {
            clazz = clazz.getSuperclass();
        }

        if (DataContractGenerator.isDataContractClass(clazz)) {
            clazz = clazz.getSuperclass();
        }

        return clazz;
    }

}
